﻿using System;
using System.Collections.Generic;

namespace System
{
    public sealed class CloneScope : IDisposable
    {
        private class DisposableStub : IDisposable
        {
            public void Dispose()
            {
            }
        }

        private Dictionary<object, object> Objs = new Dictionary<object, object>();
        //private readonly Dictionary<object, object> IDs = new Dictionary<object, object>();
        private DictionaryList<object, Action<object>> Lazies = new DictionaryList<object, Action<object>>();

        private readonly static Stack<CloneScope> stack = new Stack<CloneScope>();

        private CloneScope()
        {
        }

        public static IDisposable Create()
        {
            stack.Push(new CloneScope());
            return stack.Peek();
        }

        public static IDisposable CreateIfNeeded()
        {
            return stack.Count > 0 ? new DisposableStub() : Create();
        }

        private void add(object orig, object cloned)
        {
            if (orig != null && cloned != null)
                AttachObj(orig, cloned);
        }

        public static bool Add(object orig, object cloned)
        {
            if (stack.Count > 0)
            {
                stack.Peek().add(orig, cloned);
                return true;
            }
            return false;
        }

        private void AttachObj(object orig, object cloned)
        {
            Objs.AddOrSetValue(orig, cloned);
            foreach (var lazy in Lazies[orig])
                lazy(cloned);
            Lazies.Remove(orig);
        }

        private void getOrSuspend(object orig, Action<object> lazy)
        {
            object o;
            if (orig != null)
                if (Objs.TryGetValue(orig, out o))
                    lazy(o);
                else
                    Lazies.Add(orig, lazy);
        }

        public static bool HasScope
        {
            get { return stack.Count > 0; }
        }

        public static bool GetOrSuspend(object orig, Action<object> lazy)
        {
            if (stack.Count > 0)
            {
                stack.Peek().getOrSuspend(orig, lazy);
                return true;
            }
            return false;
        }

        public void Dispose()
        {
            //Если ссылочный объект не был клонирован, то ссылки переделываются на оригинал
            foreach (var lazy in Lazies)
                foreach (var action in lazy)
                    action(lazy.Key);
            Lazies = null;
            Objs = null;
            CloneScope cs;
            do
            {
                cs = stack.Pop();
                if (cs != this)
                    cs.Dispose();
                else
                    break;
            } while (stack.Count > 0);
        }
    }
}